package com.swak.utils;

import static java.util.Collections.emptyList;

import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

import com.swak.Constants;

public class Nets {

	private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
	private static volatile InetAddress LOCAL_ADDRESS = null;
	private static volatile NetworkInterface LOCAL_NETWORK = null;
	private static volatile String MAC = "";

	/**
	 * 网卡的 MAC 地址
	 * 
	 * @return
	 */
	public static String getHardwareAddress() {
		if (StringUtils.isNotBlank(MAC)) {
			return MAC;
		}
		NetworkInterface ni = getLocalNetwork();
		try {
			byte[] mac = ni.getHardwareAddress();
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < mac.length; i++) {
                sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
            }
			MAC = sb.toString();
		} catch (Exception e) {
		}
		return MAC;
	}

	/**
	 * 本机地址
	 *
	 * @return OS
	 */
	public static InetAddress getLocalAddress() {
		if (LOCAL_ADDRESS != null) {
			return LOCAL_ADDRESS;
		}
		InetAddress localAddress = getLocalAddress0();
		LOCAL_ADDRESS = localAddress;
		return localAddress;
	}

//	public static String toAddressString(InetSocketAddress address) {
//		return address.getAddress().getHostAddress() + ":" + address.getPort();
//	}

	static InetAddress getLocalAddress0() {
		InetAddress localAddress = null;
		try {
			NetworkInterface networkInterface = getLocalNetwork();
			Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
			while (addresses.hasMoreElements()) {
				Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
				if (addressOp.isPresent()) {
					try {
						if (addressOp.get().isReachable(100)) {
							return addressOp.get();
						}
					} catch (IOException e) {
						// ignore
					}
				}
			}
		} catch (Throwable e) {
		}

		try {
			localAddress = InetAddress.getLocalHost();
			Optional<InetAddress> addressOp = toValidAddress(localAddress);
			if (addressOp.isPresent()) {
				return addressOp.get();
			}
		} catch (Throwable e) {
		}
		return localAddress;
	}

	/**
	 * 本机网卡
	 *
	 * @return OS
	 */
	public static NetworkInterface getLocalNetwork() {
		if (LOCAL_NETWORK != null) {
			return LOCAL_NETWORK;
		}
		NetworkInterface networkInterface = findNetworkInterface();
		LOCAL_NETWORK = networkInterface;
		return LOCAL_NETWORK;
	}

	static NetworkInterface findNetworkInterface() {

		List<NetworkInterface> validNetworkInterfaces = emptyList();
		try {
			validNetworkInterfaces = getValidNetworkInterfaces();
		} catch (Throwable e) {
		}
		NetworkInterface result = null;
		for (NetworkInterface networkInterface : validNetworkInterfaces) {
			Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
			while (addresses.hasMoreElements()) {
				Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
				if (addressOp.isPresent()) {
					try {
						if (addressOp.get().isReachable(100)) {
							result = networkInterface;
							break;
						}
					} catch (IOException e) {
						// ignore
					}
				}
			}
		}

		if (result == null) {
			result = CollectionUtils.first(validNetworkInterfaces);
		}
		return result;
	}

	static List<NetworkInterface> getValidNetworkInterfaces() throws SocketException {
		List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
		Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
		while (interfaces.hasMoreElements()) {
			NetworkInterface networkInterface = interfaces.nextElement();
			if (ignoreNetworkInterface(networkInterface)) { // ignore
				continue;
			}
			validNetworkInterfaces.add(networkInterface);
		}
		return validNetworkInterfaces;
	}

	static boolean ignoreNetworkInterface(NetworkInterface networkInterface) throws SocketException {
		return networkInterface == null || networkInterface.isLoopback() || networkInterface.isVirtual()
				|| !networkInterface.isUp()
				|| StringUtils.containsIgnoreCase(networkInterface.getDisplayName(), "Virtual");
	}

	static Optional<InetAddress> toValidAddress(InetAddress address) {
		if (address instanceof Inet6Address) {
			Inet6Address v6Address = (Inet6Address) address;
			if (isPreferIPV6Address()) {
				return Optional.ofNullable(normalizeV6Address(v6Address));
			}
		}
		if (isValidV4Address(address)) {
			return Optional.of(address);
		}
		return Optional.empty();
	}

	static boolean isPreferIPV6Address() {
		return Boolean.getBoolean("java.net.preferIPv6Addresses");
	}

	static InetAddress normalizeV6Address(Inet6Address address) {
		String addr = address.getHostAddress();
		int i = addr.lastIndexOf('%');
		if (i > 0) {
			try {
				return InetAddress.getByName(addr.substring(0, i) + '%' + address.getScopeId());
			} catch (UnknownHostException e) {
			}
		}
		return address;
	}

	static boolean isValidV4Address(InetAddress address) {
		if (address == null || address.isLoopbackAddress()) {
			return false;
		}

		String name = address.getHostAddress();
		return (name != null && IP_PATTERN.matcher(name).matches() && !Constants.ANYHOST.equals(name)
				&& !Constants.LOCALHOST.equals(name));
	}

	/**
	 * 是否是组播（多播）地址：224.0.0.0 ~ 239.255.255.255 也就是D类地址： 1110（32位模式） 开头的IP地址
	 * 
	 * 1、局部多播地址：在224.0.0.0～224.0.0.255之间，这是为路由协议和其他用途保留的地址，路由器并不转发属于此范围的IP包。
	 * 2、预留多播地址：在224.0.1.0～238.255.255.255之间，可用于全球范围（如Internet）或网络协议。
	 * 3、管理权限多播地址：在239.0.0.0～239.255.255.255之间，可供组织内部使用，类似于私有IP地址，不能用于Internet，可限制多播范围。
	 * 
	 * @param host
	 * @return
	 */
	public static boolean isMulticastAddress(String host) {
		try {
			int i = host.indexOf('.');
			if (i > 0) {
				String prefix = host.substring(0, i);
				int p = Integer.parseInt(prefix);
				return p >= 224 && p <= 239;
			}
		} catch (Exception e) {
		}
		return false;
	}

	/**
	 * 广播地址：255.255.255.255
	 * 
	 * @param host
	 * @return
	 */
	public static boolean isBroadcastAddress(String host) {
		return Constants.Broadcast_Host.equals(host);
	}

	/**
	 * 檢查端口是否可用
	 * 
	 * @param port
	 * @return
	 */
	public static boolean isAvailablePort(int port) {
		try {
			Socket socket = new Socket(Constants.LOCALHOST, port);
			socket.close();
			return false;
		} catch (Exception e) {
			return true;
		}
	}

	/**
	 * 地址是否可达
	 * 
	 * @param address
	 * @return
	 */
	public static boolean isReachable(InetAddress address) {
		try {
			return address.isReachable(null, 0, 0);
		} catch (Exception e) {
			return false;
		}
	}
}
